package matching;
import graph.Node;
import graph.graph;

import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.MulticastSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;

//This is all of the xml stuff
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.PostMethod;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;



//This class will give all matches to the supervisor, makes matches
public class matchmaker {
	
	private int where;
	
	//Multicast stuff
	private MulticastSocket s;
	private byte buf[];
	private  DatagramPacket pack;
	private String promptLookForNew;

	private URL fullURL;
	private URL newestURL;
	
	//Multicast Ports
	private int multicastPortListenGateway;
	private int multicastPortListenSupe;

	//This is to get the real first InetAddr
	private InternetAddress i;

	// Which address
	String groupString;
	InetAddress group;
	boolean debug;
	int seed;

	//These all need to be synchronized as well as removed from...
	private ArrayList<item> allitems;
	private graph g;
	private ArrayList<match> matches;

	final String appendFullURL = "/rides/allOffers";
	final String appendNewestURL = "/rides/latestOffer";
	final String appendReg = "/registrants";

	public matchmaker(int _seed, boolean _debug, int _multicastPortG, int _multicastPortSM, String _groupAddress)
	{
		i = new InternetAddress();

		//Multicast Group
		groupString = _groupAddress;

		//setting multicast ports
		multicastPortListenGateway = _multicastPortG;
		multicastPortListenSupe = _multicastPortSM;

		//This makes sure that the same matchmaker
		//isn't added to the database twice!
		seed = _seed;

		//sets if debug statements are visible
		debug = _debug;

		allitems = new ArrayList<item>();
	}

	public void startThread(String url, int _where, String _supMachine, int _portNumberTCP)
	{
		String _fullURL = url.concat(appendFullURL);
		String _newestURL = url.concat(appendNewestURL);
		String _regURL = url.concat(appendReg);

		this.registerWithGateway(_regURL);

		
		System.out.print("Listening for multicast connections on port " + multicastPortListenGateway + "... ");
		
		Matching m = new Matching(_fullURL, _newestURL, _where, _supMachine, 
				_portNumberTCP);
		Thread MatchingThread = new Thread(m);
		MatchingThread.start();

	}

	public void registerWithGateway(String gatewayURL)
	{
		if(debug)
			System.out.println("Gateway:" + gatewayURL);
		PostMethod post = new PostMethod(gatewayURL);

		InetAddress inetAddress = null;


		try {
			inetAddress = i.getFirstNonLoopbackAddress(true, false);
		} catch (SocketException e1) {
			System.out.println("ISSUE USING GETNONFIRSTLOOPBACKADDRESS");
			e1.printStackTrace();
		}


		String ipAddress = inetAddress.getHostAddress();
		String multicast = groupString + ":" + multicastPortListenGateway;

		if(debug)
			System.out.println("Matchmaker Ipaddr:" + ipAddress);
		String matchmaker = "matchmaker";
		NameValuePair[] data = {
				new NameValuePair("registrant[type]", matchmaker),
				new NameValuePair("registrant[ipaddr]", ipAddress),
				new NameValuePair("registrant[multicast]", multicast),
				new NameValuePair("registrant[seed]", seed + "")//to make it a string
		};

		//sends the post request
		post.setRequestBody(data);

		// execute method and handle any error responses.
		try {
			new HttpClient().executeMethod(post);
			if(debug)
				System.out.println("Executed Post");
		} catch (HttpException e) {
			System.out.println("HTTPEXCEPTION");
			e.printStackTrace();
		} catch (IOException e) {
			System.out.println("IOEXCEPTION");
			e.printStackTrace();
		}
	}



	class Matching implements Runnable
	{
		private boolean firstRun;
		private String supMachine;
		private int portNumberTCP;
		private File xmlFile;
		final String item_element;
		final String togive_element;
		final String totake_element;
		final String howlong_element;
		final String isoffer_element;
		final String id_element;
		final String contact_element;
		final String match_element;
		ArrayList<item> toAdd;

		public Matching(String _fullURL, String _newestURL, int _where, String _supMachine,
				int _portNumberTCP)
		{
			toAdd = new ArrayList<item>();
			where = _where;
			
			//sets firstRun to true on first run
			firstRun = true;

			//multicastSocket stuff
			promptLookForNew = "new";

			//XML and matchesArrayList
			matches = new ArrayList<match>();

			supMachine = _supMachine.trim();
			portNumberTCP = _portNumberTCP;
			match_element = "match"; 
			item_element = "item";
			togive_element = "togive";
			totake_element = "totake";
			howlong_element = "howlong";
			isoffer_element = "isoffer";
			id_element = "id";
			contact_element = "contact";


			//creating URL's from Strings 
			try {
				fullURL = new URL(_fullURL);
				newestURL = new URL(_newestURL);
			} catch (MalformedURLException e) {
				e.printStackTrace();
			}
		}

		public void getXML(URL _URL, boolean redownloadOfferBook)
		{
			System.out.println("Getting the XML");
			SAXReader reader = new SAXReader();
			try {
				Document document = reader.read(_URL);
				this.fromXML(document, redownloadOfferBook);
			} catch (DocumentException e) {
				e.printStackTrace();
				System.out.println("Error reading file " + xmlFile.getAbsolutePath() + ".");
			}
		}
		private void fromXML(Document document, boolean redownloadOfferBook)
		{
			
			synchronized(toAdd)
			{
				toAdd.clear();
			}
			
			
			if(redownloadOfferBook == true)
			{
				synchronized(allitems)
				{
					allitems.clear();
				}
			}
			
			Element root = document.getRootElement();
			for (Object elementObj : root.elements(item_element)) 
			{
				Element element = (Element) elementObj;

				String togive = getStringAttribute(element,togive_element);
				String totake = getStringAttribute(element,totake_element);
				String howlong = getStringAttribute(element,howlong_element);
				String isoffer = getStringAttribute(element,isoffer_element);
				String id = getStringAttribute(element,id_element);
				String contact = getStringAttribute(element,contact_element);

				item i = new item(Integer.parseInt(id), togive, totake, Integer.parseInt(howlong), isoffer, contact);

				synchronized(toAdd)
				{
					toAdd.add(i);
				}



			}//for
			
			if(debug)
			{
				for(int i =0; i < allitems.size(); i++)
				{
					System.out.println(allitems.get(i));
				}
			}


		}
		private String getStringAttribute(Element element, String aN) 
		{

			
			Attribute a = element.attribute(aN);
			if (a == null) {
				throw new RuntimeException("Element " + element.getName() +
						" missing attribute named " + aN + ".");
			}
			
			return a.getValue();
			
		}
		private void sendXMLtoSup()
		{
			System.out.println("Sending the Matches to the Supervisor");
			//We should keep trying to send in here...
			Socket MyClient;
			try {
				MyClient = new Socket(supMachine, portNumberTCP);
				DataOutputStream os = new DataOutputStream(MyClient.getOutputStream());

				os.writeBytes(this.matchesToXML());
				os.close();
				MyClient.close();
			}
			catch (IOException e) {
				System.out.println("Sorry couldn't connect to " + supMachine + " on " + portNumberTCP +". Trying " +
				"again in 10 seconds.");
				System.err.println("ERROR:" + e);
				try {
					Thread.sleep(10000);
					this.sendXMLtoSup();
				} catch (InterruptedException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}

			}
		}
	
		public void buildGraph(ArrayList<item> toAdd)
		{
			g = new graph(debug);
			
			//This loops alls all of the items to the graph...
			for(int i =0; i < toAdd.size(); i++)
			{
				//in this loop we need to iterate through allitems, checking to see
				//if any of the togives = totakes

				//adds the node to the graph of two give and to take
				g.addNode(new Node(toAdd.get(i).getToGive(), toAdd.get(i).getToTake(), toAdd.get(i).getId()));

			}//for
			
			//There is nothing in the database or 1 thing in the database
			if(toAdd.size() == 0 || toAdd.size() == 1)
				return;
		}
		//it will literally just find all of the matches and then
		//the matches should be converted to XML
		//This method should return the number of matches it found
		public synchronized int findAllMatches(int where, boolean rebuildGraph, ArrayList<item> toAdd)
		{
			System.out.println("Finding Matches...");
			
			matches.clear();
			
			if(rebuildGraph == true)
				this.buildGraph(toAdd);
			
			//This statement finds all the cycles
			g.findCycle(where);

			ArrayList<ArrayList<Node>> cycles = g.getCycles();
			ArrayList<item> items = null;

			for(int i = 0; i < cycles.size(); i++)//go through all cycles
			{
				ArrayList<Node> c = cycles.get(i);
				items = new ArrayList<item>();

				for(int j = 0; j < cycles.get(i).size(); j++ )//go through specific cycle
				{
					Node t = c.get(j);
					String in = t.getInString();
					String out = t.getOutString();



					for(int k = 0; k < allitems.size(); k++)//change from nodes items
					{
						item it = allitems.get(k);

						if(it.getToGive().equalsIgnoreCase(in) && it.getToTake().equalsIgnoreCase(out))
						{

							System.out.println("Adding:" + it);
							items.add(it);
						}
					}//allItems

				}//go through specific cycle
				matches.add(new match(items));

			}


			if(matches.size() == 0)
			{
				System.out.println("NO MATCHES FOUND!!");

			}

			return matches.size();



		}//findAllMatches

		public String matchesToXML()
		{
			//if matches is empty break
			if(matches.size() == 0)
				return null;

			Document document = DocumentHelper.createDocument();
			Element root = document.addElement("matches");

			//iterate through all of the matches 

			for (int i = 0; i < matches.size(); i++) {
				Element t = root.addElement(match_element);
				for(int j = 0; j < matches.get(i).getMatchSize(); j++)
				{
					//no 2 elements can be the same so this if statement adds
					//the first normal element and then adds from there with j...
					if(j ==0)
					{
						t.addAttribute("matchsize", matches.get(i).getMatchSize()+"")
						.addAttribute(id_element, matches.get(i).getItem(j).getId()+"")


						.addAttribute(togive_element, matches.get(i).getItem(j).getToGive())


						.addAttribute(totake_element, matches.get(i).getItem(j).getToTake())


						.addAttribute(howlong_element, matches.get(i).getItem(j).getHowLong()+"")


						.addAttribute(isoffer_element, matches.get(i).getItem(j).getIsOffer())


						.addAttribute(contact_element, matches.get(i).getItem(j).getContact());

					}
					else
					{
						int j2 = j+1;
						t.addAttribute(id_element + j2, matches.get(i).getItem(j).getId()+"")
						//.addAttribute(id_element2, matches.get(i).getItem2().getId()+"")

						.addAttribute(togive_element + j2, matches.get(i).getItem(j).getToGive())
						//.addAttribute(togive_element2, matches.get(i).getItem2().getToGive())

						.addAttribute(totake_element + j2, matches.get(i).getItem(j).getToTake())
						//.addAttribute(totake_element2, matches.get(i).getItem2().getToTake())

						.addAttribute(howlong_element + j2, matches.get(i).getItem(j).getHowLong()+"")
						//  .addAttribute(howlong_element2, matches.get(i).getItem2().getHowLong()+"")

						.addAttribute(isoffer_element + j2, matches.get(i).getItem(j).getIsOffer())
						//.addAttribute(isoffer_element2, matches.get(i).getItem2().getIsOffer())

						.addAttribute(contact_element + j2, matches.get(i).getItem(j).getContact());
						//	.addAttribute(contact_element2, matches.get(i).getItem2().getContact());
					}

				}


			}
			String t = document.asXML();
			return t;

		}

		public void run()
		{
			//This starts the threads for the add and remove
			RemoveListener r = new RemoveListener();
			Thread RemoveListenerThread = new Thread(r);
			RemoveListenerThread.start();
			
			AddListener a = new AddListener();
			Thread addThread = new Thread(a);
			addThread.start();

			//this if downloads the all of the offers
			//if there are any then send them to the supervisor
			if(firstRun == true)
			{
				System.out.println("\nDownloading the entire offerbook!!!\n");
				getXML(fullURL , firstRun);
				if(findAllMatches(where, firstRun, toAdd) > 0)
					sendXMLtoSup();
				firstRun = false;
			}//firstRun IF
			else
			{
				getXML(newestURL, firstRun);
				if(findAllMatches(where, firstRun, toAdd) > 0)
					this.sendXMLtoSup();
			}

			

			


		}//run
		

		class RemoveListener implements Runnable
		{
			//Multicast from Supe Stuff
			private MulticastSocket ss;
			private byte buff[];
			private DatagramPacket packer;
			private ArrayList<Integer> ints;

			public RemoveListener()
			{

			}

			public void run() {
				//While loop to keep looping
				try {
					group  = InetAddress.getByName(groupString);
					ss = new MulticastSocket(multicastPortListenSupe);
					ss.joinGroup(group);

					while(true)
					{
						try{
							buff = new byte[1024];
							packer = new DatagramPacket(buff, buff.length);

							ss.receive(packer);//This is where it is blocking....
							System.out.println("Removing from our data structures");

							String line = new String(packer.getData());


							if(line.startsWith("remove"))
							{
								String[] a = line.split("\\s+");
								ints = new ArrayList<Integer>();
								for(int i = 0; i < a.length; i++)
								{
									if(!a[i].equalsIgnoreCase("remove"))
									{
										ints.add(Integer.parseInt(a[i].trim()));

										System.out.println("Remove ID: " + a[i]);
									}//if
								}//for

							}//remove if

							ArrayList<Node> toRemoveFromGraph = new ArrayList<Node>();

							if(ints.size() > 0)
							{
								Collections.sort(ints);
								synchronized(allitems)
								{
									for(int i = 0; i < allitems.size(); i++)
									{
										for(int j = 0; j < ints.size(); j++)
										{
											if(allitems.get(i).getId() == ints.get(j))
											{
												//This adds to an arraylist that will be used to remove from the graph later..
												Node n = g.findNode(ints.get(j));
												toRemoveFromGraph.add(n);

												allitems.remove(i);

											}//if

										}//for ints size

									}//allitems 

								}//sync all items

								synchronized(matches)
								{
									for(int x = 0; x < matches.size(); x++)//Go through all matches
									{	
										//if we find that match in matches, remove it!
										if(matches.get(x).compareIds(ints) == true)
										{
											matches.remove(x);
										}

									}//for matches
								}//sync matches

								synchronized(g)
								{
									g.removeNodes(toRemoveFromGraph);
								}
							}//if ints


						}catch (UnknownHostException e1) {
							System.out.println("Issue!: Unknown Host!");
							e1.printStackTrace();}


					}//while
				}//try
				catch (IOException ex) {  System.out.println("Exception: "+ ex); }

			}

		}
	
		class AddListener implements Runnable
		{
		
			public AddListener()
			{
			
			}
			
			public void run() {
				try {
					group  = InetAddress.getByName(groupString);
					s = new MulticastSocket(multicastPortListenGateway);
					s.joinGroup(group);
					if(debug)
						System.out.println("Created multicastListener Thread. Group:" + group);
					while(true)
					{
						try{
							buf = new byte[1024];
							pack = new DatagramPacket(buf, buf.length);

							s.receive(pack);//This is where it is blocking....

							String line = new String(pack.getData());

							if(debug)
							{
								System.out.println("After Recieve getData call: " + line + ".");
								System.out.println("promptLookForNew: " + promptLookForNew + ".");
								System.out.println("EqualsIgnoreCase?" + line.equalsIgnoreCase(promptLookForNew));
								System.out.println("Startswith?" + line.startsWith(promptLookForNew));
								System.out.println("Line length=" + line.length() + " promptLookForNew length" + promptLookForNew.length());

							}

							if(line.trim().equalsIgnoreCase(promptLookForNew))
							{
								
								System.out.println("About to get XML from: " + newestURL);
								getXML(newestURL , false); // false because its the newestURL. Don't need to redownload the offer book...
								if(findAllMatches(where, firstRun, toAdd) > 0)
									sendXMLtoSup();
							}


							if (line.equalsIgnoreCase("down")) {
								if(debug)
									System.out.println("Server is shutting down");
								s.leaveGroup(group);
								s.close();
								break;
							}
						}catch (UnknownHostException e1) {
							System.out.println("Exiting...");
							e1.printStackTrace();}


					}//while
				}//try
				catch (IOException ex) {  System.out.println("Exception: "+ ex); }
			}
			
		}
		
	}//class Matching

	
}//class matchmaker
